package com.asdhawaii.ics111game.item;

import java.util.ArrayList;
import java.util.List;

/**
 * An item in the game world.
 * 
 * @author Branden Ogata
 *
 */

public class Item
{
  /**
   * The name of this Item.
   * 
   */
  private String name;
  
  /**
   * A short description of this Item.
   * 
   */
  private String shortDescription;
  
  /**
   * A long description of this Item.
   * 
   */
  private String longDescription;
  
  /**
   * Indicates if this Item is edible.
   * 
   */
  private boolean edible;
  
  /**
   * Indicates if this Item is drinkable.
   * 
   */
  private boolean drinkable;
  
  /**
   * Indicates if this Item is movable.
   * 
   */
  private boolean movable;
  
  /**
   * The monetary value of this Item in dollars and cents.
   * 
   */
  private double value;
  
  /**
   * The total mass of this Item.
   * 
   */
  private double mass;
  
  /**
   * The total volume of this Item.
   * 
   */
  private double volume;
  
  /**
   * Indicates if this Item can contain other Item instances.
   * 
   */
  private boolean container;
  
  /**
   * The Item instances contained within this Item.
   * 
   */
  private List<Item> contents;
  
  /**
   * Initializes the fields for a new Item.
   * 
   * @param name The String containing the name of the new Item.
   * @param shortDescription The String containing the short description of the new Item.
   * @param longDescription The String containing the long description of the new Item.
   * @param edible The boolean indicating if the new Item is edible.
   * @param drinkable The boolean indicating if the new Item is drinkable.
   * @param value The double equal to the monetary value of the new Item.
   * @param mass The double equal to the mass of the new Item.
   * @param volume The double equal to the volume of the new Item.
   * @param container The boolean indicating if the new Item is a container.
   * 
   */
  
  public Item(String name, String shortDescription, String longDescription, boolean edible,
              boolean drinkable, boolean movable, double value, double mass, double volume, 
              boolean container)
  {
    this.name = name;
    this.shortDescription = shortDescription;
    this.longDescription = longDescription;
    this.edible = edible;
    this.drinkable = drinkable;
    this.movable = movable;
    this.value = value;
    this.mass = mass;
    this.volume = volume;
    this.container = container;
    this.contents = new ArrayList<Item>();
  }
  
  /**
   * Returns the name of this Item.
   * 
   * @return this.name The String containing the name of this Item.
   * 
   */
  
  public String getName()
  {
    return this.name;
  }
  
  /**
   * Returns the short description of this Item.
   * Used in response to a LOOK command.
   * 
   * @return this.shortDescription The String containing the short description of this Item.
   * 
   */
  
  public String getShortDescription()
  {
    return this.shortDescription;
  }
  
  /**
   * Returns the long description of this Item.
   * Used in response to an EXAMINE command.
   * 
   * @return this.longDescription The String containing the long description of this Item.
   * 
   */
  
  public String getLongDescription()
  {
    return this.longDescription;
  }
  
  /**
   * Indicates if this Item is edible.
   * Involved in the response to an EAT command.
   * 
   * @return this.edible The boolean indicating if this Item is edible.
   * 
   */
  
  public boolean isEdible()
  {
    return this.edible;
  }
  
  /**
   * Indicates if this Item is drinkable.
   * Involved in the response to a DRINK command.
   * 
   * @return this.drinkable The boolean indicating if this Item is drinkable.
   * 
   */
  
  public boolean isDrinkable()
  {
    return this.drinkable;
  }
  
  /**
   * Indicates if this Item is movable.
   * 
   * @return this.moveable The boolean indicating if this Item is movable.
   */
  
  public boolean isMovable()
  {
    return this.movable;
  }
  
  /**
   * Returns the monetary value of this Item.
   * 
   * @return this.value The double equal to the monetary value of this Item.
   * 
   */
  
  public double getValue()
  {
    return this.value;
  }
  
  /**
   * Updates the monetary value of this Item based on the amount given as a parameter.
   * 
   * @param delta The double equal to the amount by which the value should change.
   * 
   * @return success A boolean that is true if the update was successful.
   * 
   */
  
  public boolean updateValue(double delta)
  {
    boolean success = false;
    
    // If the delta will not result in a negative value, then update
    if (this.value + delta >= 0)
    {
      this.value += delta;
      success = true;
    }
    
    return success;
  }
  
  /**
   * Returns the mass of this Item.
   * 
   * @return this.mass The double equal to the mass of this Item.
   * 
   */
  
  public double getMass()
  {
    return this.mass;
  }
  
  /**
   * Returns the volume of this Item.
   * 
   * @return this.volume The double equal to the volume of this system.
   * 
   */
  public double getVolume()
  {
    return this.volume;
  }
  
  /**
   * Indicates whether or not this Item can contain other objects.
   * 
   * @return this.container A boolean that is true if this Item is a container.
   * 
   */
  
  public boolean isContainer()
  {
    return this.container;
  }
  
  /**
   * Prints a hierarchical list of the items in this Item.
   * 
   * @return contents A String containing the list of items in this Item if any.
   * 
   */
  
  public String getContents()
  {
    String contents = "";
    
    // For each item in contents
    for (Item i : this.contents)
    {
      // Append the item itself
      contents += String.format("%s%n", i);
      
      // If the item contains any items itself, get that list divided into separate lines
      if ((i.isContainer() == true) && (!i.contents.isEmpty()))
      {
        String[] subItems = i.getContents().split(String.format("%n"));
        
        // Print each item on a separate line, indented
        for (String s : subItems)
        {
          contents += String.format("  %s%n", s);
        }
      }
    }
    
    return contents;
  }
  
  /**
   * Adds an Item instance to the contents of this Item if possible.
   * 
   * @param item The Item to put into this Item.
   * 
   * @return added A boolean that is true if the parameter was added,
   *                                 false otherwise.
   *                                 
   */
  
  public boolean addItem(Item item)
  {
    boolean added = false;
    
    // If this Item is a container, then can add
    if (this.isContainer())
    {
      added = true;
      this.contents.add(item);
    }
    
    return added;
  }
  
  /**
   * Removes an Item instance from the contents of this Item if it is present.
   * Using a String parameter makes this process more complicated,
   * but since the programmer has no idea if the calling class will have a reference
   * to the Item instance to remove, a String is the more certain means.
   * 
   * @param item A String equal to the name of the Item to remove.
   * 
   * @return target The Item removed from the contents of this Item;
   *                null if the target was not found.
   *                
   */
  
  public Item removeItem(String item)
  {
    Item target = null;
    
    // Search through all of the contents of this Item
    if (this.isContainer())
    {
      for (Item i : this.contents)
      {
        // If the String parameter matches the name of the Item,
        // then the desired Item is in the contents of this Item
        // and therefore can be removed
        if (item.equals(i.getName()))
        {
          target = i;
        }
      }
    }

    // If the above search found the desired Item, remove it
    if (target != null) 
    {
      this.contents.remove(target);
    }
    
    return target;
  }
  
  /**
   * Removes and returns all Item instances from the contents of this Item.
   * 
   * @return allItems The List<Item> of Item instances that this Item contained.
   * 
   */
  
  public List<Item> removeAll()
  {
    List<Item> allItems = new ArrayList<Item>();
    allItems.addAll(this.contents);
    
    return allItems;
  }
  
  /**
   * Compares this Item to the parameter.
   * 
   * @param obj The object to compare this Item to.
   * 
   * @return isEqual A boolean that is true if this Item is equal to the parameter, 
   *                                   false otherwise.
   * 
   */
  
  @Override
  public boolean equals(Object obj)
  {
    boolean isEqual = false;
    
    if (obj == this)
    {
      isEqual = true;
    }
    else if (obj instanceof Item)
    {
      Item temp = (Item) obj;
      
      // If the names are equal,then the two Item instances are equal
      // Not the best way to determine equality, but it will have to do for now
      if (this.getName().equalsIgnoreCase(temp.getName()))
      {
        isEqual = true;
      }
    }
    
    return isEqual;
  }
  
  /**
   * Returns a hash code based on this Item object.
   * Based on the suggestions in Effective Java, Chapter 3.
   * 
   * @return result An int equal to the hash code for this Item object.
   * 
   */
  
  @Override
  public int hashCode()
  {
    int result = 17;
    int combination = this.name.toLowerCase().hashCode();
    result = 37 * result + combination;
    return result;
  }
  
  /**
   * Compares this Item to another Item object.
   * This comparison is based on the names of each Item.
   * 
   * @param i The Item to compare this Item to.
   * 
   * @return 0 if the objects are equal.
   *         1 if this object is greater than the parameter object.
   *         -1 if this object is less than the parameter object.
   *         
   */
  
  public int compareTo(Item i)
  {
    // If this object is the parameter object, then by definition they are equal
    if (this == i)
    {
      return 0;
    }
    
    // Compare item names
    if (this.getName().toLowerCase().compareTo(i.getName().toLowerCase()) > 0)
    {
      return 1;
    }
    else if (this.getName().toLowerCase().compareTo(i.getName().toLowerCase()) < 0) 
    {
      return -1;
    }
    
    return 0;
  }
  
  /**
   * Returns a String description of this Item.
   * Essentially returns the name of this Item.
   * 
   * @return A String describing this Item.
   * 
   */
  
  @Override
  public String toString()
  {
    return this.name;
  }
}
